// // Copyright (c) 2009 All Right Reserved // // vl // // 2009-01-01 // Contains ... using System; using System.Collections.Generic; using System.Diagnostics.Contracts; using System.IO; using System.IO.Compression; using System.Linq; using System.Xml.Linq; using LargoCommon.Abstract; using LargoCommon.Localization; using LargoCommon.Music; namespace LargoCommon.Support { /// /// Music Xml. /// public sealed class MusicXmlReader { #region Fields /// /// Musical Block. /// private MusicalBlock musicalBlock; /// /// The linearizer /// private MusicalLinearizer linearizer; #endregion #region Constructors #endregion #region Properties /// /// Gets Musical Block. /// /// Property description. /// /// Gets musical block. /// /// Property description. public MusicalBlock Block { get { Contract.Ensures(Contract.Result() != null); if (this.musicalBlock == null) { throw new InvalidOperationException("Musical block is null."); } return this.musicalBlock; } private set => this.musicalBlock = value ?? throw new ArgumentException("Argument cannot be null.", nameof(value)); } /// /// Gets Common Division. /// /// Property description. public int CommonDivision { get; private set; } /// /// Gets Local Division. /// /// Property description. public int LocalDivision { get; private set; } /// /// Gets or sets Local Pitch Shift. /// /// Property description. public int LocalPitchShift { get; set; } /// /// Gets or sets Header. /// private MusicXmlHeader Header { get; set; } /// /// Gets or sets Musical File. /// /// Property description. private MusicalBundle MusicalBundle { get; set; } #endregion #region Music Xml /// /// Read MusicXml file.. /// /// Musical file path. /// Name of the internal. /// /// Returns value. /// public static MusicalBundle ReadMusicXmlFile(string path, string internalName) { Contract.Requires(path != null); if (string.IsNullOrEmpty(path)) { return null; } //// string fileName = Path.GetFileNameWithoutExtension(path); //// WinAbstract logger = this.Window.EditorWindow.OpenWindow("InherentLogger", null); var musicXmlReader = new MusicXmlReader(); var musicXmlDocument = XDocument.Load(path); ProcessLogger.Singleton.SendLogEvent(Path.GetFileName(path), LocalizedMusic.String("Reading MusicXml file ... "), 0); var musicalBundle = musicXmlReader.ExtractMusicalFile(musicXmlDocument, path, internalName); //// musicalBundle.MidFileId = midiFile.Id; if (musicalBundle == null) { return null; } //// string name = Path.GetFileNameWithoutExtension(path); musicalBundle.FileName = internalName.ClearSpecialChars(); //// name ?? "Unknown"; return musicalBundle; } #endregion #region Music Mxl /// /// Extract Musical File from the Music Xml. /// /// Musical file path. /// Name of the internal. /// /// Returns value. /// public static MusicalBundle ReadMusicMxlFile(string path, string internalName) { Contract.Requires(path != null); Contract.Requires(path.Length != 0); if (string.IsNullOrEmpty(path)) { return null; } ProcessLogger.Singleton.SendLogEvent(path, LocalizedMusic.String("Reading MusicMxl file ... "), 0); var tempFolder = Path.GetTempPath(); var subfolder = Guid.NewGuid().ToString(); var subfolderPath = Path.Combine(tempFolder, subfolder); if (string.IsNullOrEmpty(subfolderPath)) { return null; } Directory.CreateDirectory(subfolderPath); ZipFile.ExtractToDirectory(path, subfolderPath); //// ZipFileCover.UnzipFile(path, subfolderPath); var fi = SupportFiles.LatestFile(subfolderPath, "*.xml"); if (fi == null) { return null; } var musicXmlReader = new MusicXmlReader(); var musicXmlDocument = XDocument.Load(fi.FullName); var musicalBundle = musicXmlReader.ExtractMusicalFile(musicXmlDocument, path, internalName); if (musicalBundle == null) { return null; } //// string name = Path.GetFileNameWithoutExtension(path); musicalBundle.FileName = internalName.ClearSpecialChars(); //// name ?? "Unknown"; return musicalBundle; } #endregion /// /// Determines the rhythmic order. /// /// The division. /// Returns value. private static int DetermineRhythmicOrder(int division) { const int p2 = 2; const int p3 = 3; const int p5 = 5; while (division > DefaultValue.MaximumRhythmicOrder) { if (division % p2 == 0) { division = division / p2; } else { if (division % p3 == 0) { division = division / p3; } else { if (division % p5 == 0) { division = division / p5; } } } } return division; } #region Private methods /// /// Extract Musical File from the Music Xml. /// /// Xml document. /// The path. /// Name of the internal. /// /// Returns value. /// private MusicalBundle ExtractMusicalFile(XDocument musicXmlDocument, string path, string internalName) { //// const int rhythmicDivisionLimit = 250; Contract.Requires(musicXmlDocument != null); if (musicXmlDocument == null) { return null; } var name = internalName.ClearSpecialChars(); var justPath = Path.GetDirectoryName(path); this.Block = new MusicalBlock { Header = new MusicalHeader() { Name = name, //// FilePath = Path.Combine(justPath ?? throw new InvalidOperationException(), name), FileName = name, System = { HarmonicOrder = DefaultValue.HarmonicOrder }, Tempo = DefaultValue.DefaultTempo, } }; this.MusicalBundle = MusicalBundle.GetEnvelopeOfBlock(this.Block, string.Empty); this.MusicalBundle.OriginType = MusicalOriginType.Original; //// Score Partwise var scorePartwise = musicXmlDocument.Root; //// element name ="score-partwise" this.Header = new MusicXmlHeader(scorePartwise, this.Block); this.Header.ReadXmlHeader(); //// ProcessLogger.Singleton.SendLogEvent(LocalizedMusic.String("Preparing file..."), 0); this.CommonDivision = 1; if (musicXmlDocument.Root != null) { var parts = musicXmlDocument.Root.Elements("part"); var plist = parts as IList ?? parts.ToList(); //// resharper foreach (var part in plist) { this.ReadMusicalDivision(part); } var d = DetermineRhythmicOrder(this.CommonDivision); this.Block.Header.System.RhythmicOrder = (byte)d; //// this.MusicalBlock.Division this.Block.Header.Division = this.CommonDivision; //// if (this.MusicalBlock.Division > rhythmicDivisionLimit) { //// If rhythm coded in arithmetic of 3-symbols then limit is 523 //// throw new ArgumentException("Rhythmical order can not exceed " + rhythmicDivisionLimit + " !!!"); } this.AppendParts(plist); } this.linearizer.TransferPartsToTracks(true); var context = new MusicalContext(MusicalSettings.Singleton, this.Block.Header); this.Block.Strip = new MusicalStrip(context); foreach (var t in this.linearizer.Lines) { this.Block.AddLine(t); } this.Block.LoadFirstStatusToLines(); //// 2019/10 this.Block.ConvertStripToBody(false); this.Block.Body.SetBodyStatusFromTones(); return this.MusicalBundle; } /// /// Read Musical Part. /// /// Musical part. private void ReadMusicalDivision(XContainer part) { Contract.Requires(part != null); var measures = part.Elements("measure"); //// int barNumber = 0; foreach (var measure in measures) { //// byte numberOfStaves = 1; //// barNumber++; var attributes = measure.Element("attributes"); if (attributes == null) { continue; } var time = attributes.Element("time"); if (time != null) { var symbol = (string)time.Attribute("symbol"); if (string.CompareOrdinal(symbol, "cut") == 0) { this.Block.Header.Metric.MetricBeat = 4; this.Block.Header.Metric.MetricBase = 2; } else { var b = time.Element("beats"); if (b != null && !b.IsEmpty) { this.Block.Header.Metric.MetricBeat = (byte)(int)b; } var bt = time.Element("beat-type"); if (bt != null && !bt.IsEmpty) { var metricGround = (byte)(int)bt; this.Block.Header.Metric.MetricBase = MusicalProperties.GetMetricBase(metricGround); } } } var divisions = attributes.Element("divisions"); if (divisions == null) { continue; } var beatDivision = (int)divisions; this.LocalDivision = this.Block.Header.Metric.MetricBeat * beatDivision; this.CommonDivision = (int)MathSupport.LeastCommonMultiple(this.CommonDivision, this.LocalDivision); //// string staves = (string)attributes.Element("staves"); //// if (staves != null) { numberOfStaves = byte.Parse(staves); } } } /// /// Read Musical Part. /// /// Musical part. /// Line number. /// Returns value. private MusicalPart ReadMusicalPart(XElement part, int lineIndex) { Contract.Requires(part != null); var partId = (string)part.Attribute("id"); var scorePartObject = this.Header.ScoreParts[partId]; var musicalPart = MusicalPart.GetNewMusicalPart(this.Block, lineIndex, scorePartObject.MidiChannel); musicalPart.PartId = partId; musicalPart.Purpose = LinePurpose.Fixed; //// 2019/01 LinePurpose.Fixed; var measures = part.Elements("measure"); var barNumber = 0; foreach (var measure in measures) { barNumber++; var attributes = measure.Element("attributes"); var divisions = attributes?.Element("divisions"); if (divisions != null) { var beatDivision = (int)divisions; this.LocalDivision = this.Block.Header.Metric.MetricBeat * beatDivision; } var musicXmlMeasure = new MusicXmlMeasure(this.Header); musicXmlMeasure.ReadMusicalBar(scorePartObject, musicalPart, measure, barNumber, this); } this.Block.Header.NumberOfBars = barNumber; musicalPart.LayObjectsToVoiceTracks(); return musicalPart; } /// /// Appends the parts. /// /// The list of parts. private void AppendParts(IEnumerable plist) { Contract.Requires(plist != null); this.linearizer = new MusicalLinearizer(null); //// block.header !?! byte partNumber = 0; foreach (var part in plist) { var musicalPart = this.ReadMusicalPart(part, ++partNumber); var scorePartObject = this.Header.ScoreParts[musicalPart.PartId]; this.LocalPitchShift = 0; if (scorePartObject != null) { musicalPart.Instrument = new MusicalInstrument((MidiMelodicInstrument)scorePartObject.MidiProgram); musicalPart.Channel = scorePartObject.MidiChannel; } //// musicalPart.MusicalBlock = this.Block; musicalPart.Purpose = LinePurpose.Fixed; //// 2019/01 LinePurpose.Fixed //// track.LineType = MusicalLineType.Melodic; //// track.Status.LineType = MusicalLineType.Melodic; this.linearizer.Parts.Add(musicalPart); } } #endregion } }